SDカードを使う(SD_MMC編)

更新日: 2026.03.27

概要

ESP32、ESP32S3には SDカードのとの接続方法に SPIとは別に SD_MMC 接続が有ります。 今回は SD_MMC接続に付いて説明します。

SD_MMCとは

SD_MMCとは、SDカードをより高速に、かつ効率的に読み書きするためのインターフェースです。 主な特徴は以下の通り。

  • 高速転送:
    • SPI通信に対し動作クロックが速く、複数のデータ線を使うため読み書きの速度が速い。
  • 項目SD_MMC (4-bit)SD_MMC (1-bit)SPIモード
    データ線数4本1本1本 (MOSI/MISO)
    使用ピン数6本3本4本
    転送速度最高速 (約10-20MB/s)中速 (約4-8MB/s)低速 (約1-2MB/s)
    ピン割り当て固定 (基本変更不可)固定 (基本変更不可)自由 (任意のGPIO)
    対応モデル無印, S3, P4など無印, S3, P4など全モデル (C3, C6, 8266含)
    動作周波数約40MHz約40MHz約8MHz
    ライブラリSD_MMC.hSD_MMC.hSD.h
  • ハードウェア支援:
    • ESP32は SD_MMC コントローラーを内蔵。CPUの負荷を抑えながら安定した通信が可能。

主な関数

*初期化・システム管理

関数名引数戻り値説明
beginmountpointmode1bit
format_if_mount_failed
boolSDカードをマウント(初期化)。
1bitモードか4bitモードかを選択可能。
endなしなしマウントを解除し、リソースを解放します。
cardTypeなしsdcard_type_tカードの種類(SDHC, SDXC, MMC等)
を返します。
totalBytesなしuint64_tSDカードの総容量(バイト単位)
を返します。
usedBytesなしuint64_t使用中の容量(バイト単位)を返します。

*ファイル・ディレクトリ操作

関数名引数戻り値説明
openpathmodeFileファイルまたはディレクトリを開きます。modeには
 FILE_READFILE_WRITEFILE_APPEND を指定します。
existspathbool指定したパスにファイルやディレクトリが
存在するか確認します。
mkdirpathbool新しいディレクトリを作成します。
removepathbool指定したファイルを削除します。
rmdirpathbool指定したディレクトリを削除します。
renamepathFrompathToboolファイル名やディレクトリ名を変更します。

*Fileオブジェクトの操作

関数名引数戻り値説明
print / printlndatasize_tファイルに文字列や数値を書き込みます。
writebufsizesize_tバイナリデータをファイルに書き込みます。
readなし / bufsizeint / size_tデータを1バイト、
または指定サイズ分読み込みます。
availableなしint読み取り可能な残りのバイト数を返します。
seekposboolファイル内の読み書き位置(ポインタ)
を指定位置に移動します。
sizeなしuint32_tそのファイルのサイズ(バイト)を返します。
closeなしなしファイルを閉じ、変更を確定させます。

機能確認

配線

配線前に、SPIとSD_MMC で実際に使用するピンを下記に比較。SD_MMCに関し、 ESP32はデフォルトで固定。 ESP32S3にはデフォルトが有りません。ユーザが指定します。ESP32S3の方が使い勝手が良い。

信号名SD_MMC (4-bit)SD_MMC (1-bit)SPI (標準VSPI)備考
CLK / SCKGPIO 14GPIO 14GPIO 18クロック
CMD / MOSIGPIO 15GPIO 15GPIO 23コマンド / データ送信
D0 / MISOGPIO 2GPIO 2GPIO 19データ線0 / データ受信
D3 / CSGPIO 13解放GPIO 5データ線3 / チップセレクト
D1GPIO 4解放解放
D2GPIO 12解放解放

下記はESP32,S3とMMC(1-Line)の接続例です。各信号線は10KΩ位でPullupする必要が有ります。

Schematic of SD_MMC for ESP32,S3
  • GPIO2には注意が必要
    • スケッチを書き込む時、このピンをLowにする必要が有る。
    • SD_MMCではHighにしなければいけない。
    • 10kΩの抵抗が有れば大抵動作する様ですが、動かない事もある様です。
    • "SD_MMC library" ではスケッチ書き込みの前後でスイッチ等でHigh/Lowを切り替えを推奨。
  • 配線の長さとプルアップ抵抗に注意
    • 転送速度が速いのでSPIより更に配線の長さに注意が必要です。
    • SPIの時が約20cmを目安にしていました。それ以下が理想。
    • プルアップ抵抗が無いと誤動作します。確実に入れて下さい。
  • CSは抵抗でプルアップすれば良い。
    • MMC(1-Line)で実質必要な信号ラインは3本。
    • SPIが4本なので、1本少ない。意外とこれが大きなメリット。

サンプルプログラム

SD_MMCのサンプルプログラムは スケッチ例ー>SD_MMCー>SDMMC_Testに有ります。 回路が、ESP32S3 の MMC(1-Line) なのでサンプルスケッチの以下を変更しています。

  • 信号ラインは上の配線図に合わせて配線しています。(スケッチの215から217行参照)  
    • #define mmc_CMD: 18
      #define mmc_CLK: 17
      #define mmc_D0:  16
  • 236行 mmc_D0ピンは High 保持なので、立ち上がり直後にセット。
  • 237行 SD_MMC.setPins(mmc_CLK, mmc_CMD, mmc_D0);で各信号線を設定。
  • 238行 SD_MMC.begin("/sdmmc", true, false, SDMMC_FREQ, MAX_FILE)で下記を設定。
  • 第1〜5引数役割設定のポイント
    "/sdmmc"マウントポイントファイルシステムのルート名です。
    /sdcard/sd など、コード内でファイルを
    指定する際のプレフィックスになります。
    true1ビットモード有効化true で1ビット(D0のみ)通信
    false で4ビット(D0-D3)通信。
    false自動フォーマットtrue にするとマウント失敗時にSDカードを
    強制初期化します。データ保護のため通常は
    false 推奨です。
    SDMMC_FREQ動作周波数 (kHz)通信スピードを指定します。デフォルトは
    通常 20000 (20MHz) です。高速なカードなら
    40000 (40MHz)などに上げられますが、
    ノイズに弱くなります。
    MAX_FILE同時オープンファイル数同時に開くファイル数を指定。
    デフォルトは 5 程度。

変更点は以上です。

test.ino arduino
/* * pin 1 - D2 | Micro SD card | * pin 2 - D3 | / * pin 3 - CMD | |__ * pin 4 - VDD (3.3V) | | * pin 5 - CLK | 8 7 6 5 4 3 2 1 / * pin 6 - VSS (GND) | ▄ ▄ ▄ ▄ ▄ ▄ ▄ ▄ / * pin 7 - D0 | ▀ ▀ █ ▀ █ ▀ ▀ ▀ | * pin 8 - D1 |_________________| * ║ ║ ║ ║ ║ ║ ║ ║ * ╔═══════╝ ║ ║ ║ ║ ║ ║ ╚═════════╗ * ║ ║ ║ ║ ║ ║ ╚══════╗ ║ * ║ ╔═════╝ ║ ║ ║ ╚═════╗ ║ ║ * Connections for ║ ║ ╔═══╩═║═║═══╗ ║ ║ ║ * full-sized ║ ║ ║ ╔═╝ ║ ║ ║ ║ ║ * SD card ║ ║ ║ ║ ║ ║ ║ ║ ║ * ESP32-P4 Func EV | 40 39 GND 43 3V3 GND 44 43 42 | SLOT 0 (IO_MUX) * ESP32-S3 DevKit | 21 47 GND 39 3V3 GND 40 41 42 | * ESP32-S3-USB-OTG | 38 37 GND 36 3V3 GND 35 34 33 | * ESP32 | 4 2 GND 14 3V3 GND 15 13 12 | * Pin name | D1 D0 VSS CLK VDD VSS CMD D3 D2 | * SD pin number | 8 7 6 5 4 3 2 1 9 / * | █/ * |__▍___▊___█___█___█___█___█___█___/ * WARNING: ALL data pins must be pulled up to 3.3V with an external 10k Ohm resistor! * Note to ESP32 pin 2 (D0): Add a 1K Ohm pull-up resistor to 3.3V after flashing * * SD Card | ESP32 * D2 12 * D3 13 * CMD 15 * VSS GND * VDD 3.3V * CLK 14 * VSS GND * D0 2 (add 1K pull up after flashing) * D1 4 * * For more info see file README.md in this library or on URL: * https://github.com/espressif/arduino-esp32/tree/master/libraries/SD_MMC */ #include "FS.h" #include "SD_MMC.h" #ifdef CONFIG_IDF_TARGET_ESP32S3 // Default pins for ESP-S3 // Warning: ESP32-S3-WROOM-2 is using most of the default GPIOs (33-37) to interface with on-board OPI flash. // If the SD_MMC is initialized with default pins it will result in rebooting loop - please // reassign the pins elsewhere using the mentioned command `setPins`. // Note: ESP32-S3-WROOM-1 does not have GPIO 33 and 34 broken out. // Note: if it's ok to use default pins, you do not need to call the setPins int clk = 36; int cmd = 35; int d0 = 37; int d1 = 38; int d2 = 33; int d3 = 39; // GPIO 34 is not broken-out on ESP32-S3-DevKitC-1 v1.1 #endif void listDir(fs::FS &fs, const char *dirname, uint8_t levels) { Serial.printf("Listing directory: %s\n", dirname); File root = fs.open(dirname); if (!root) { Serial.println("Failed to open directory"); return; } if (!root.isDirectory()) { Serial.println("Not a directory"); return; } File file = root.openNextFile(); while (file) { if (file.isDirectory()) { Serial.print(" DIR : "); Serial.println(file.name()); if (levels) { listDir(fs, file.path(), levels - 1); } } else { Serial.print(" FILE: "); Serial.print(file.name()); Serial.print(" SIZE: "); Serial.println(file.size()); } file = root.openNextFile(); } } void createDir(fs::FS &fs, const char *path) { Serial.printf("Creating Dir: %s\n", path); if (fs.mkdir(path)) { Serial.println("Dir created"); } else { Serial.println("mkdir failed"); } } void removeDir(fs::FS &fs, const char *path) { Serial.printf("Removing Dir: %s\n", path); if (fs.rmdir(path)) { Serial.println("Dir removed"); } else { Serial.println("rmdir failed"); } } void readFile(fs::FS &fs, const char *path) { Serial.printf("Reading file: %s\n", path); File file = fs.open(path); if (!file) { Serial.println("Failed to open file for reading"); return; } Serial.print("Read from file: "); while (file.available()) { Serial.write(file.read()); } } void writeFile(fs::FS &fs, const char *path, const char *message) { Serial.printf("Writing file: %s\n", path); File file = fs.open(path, FILE_WRITE); if (!file) { Serial.println("Failed to open file for writing"); return; } if (file.print(message)) { Serial.println("File written"); } else { Serial.println("Write failed"); } } void appendFile(fs::FS &fs, const char *path, const char *message) { Serial.printf("Appending to file: %s\n", path); File file = fs.open(path, FILE_APPEND); if (!file) { Serial.println("Failed to open file for appending"); return; } if (file.print(message)) { Serial.println("Message appended"); } else { Serial.println("Append failed"); } } void renameFile(fs::FS &fs, const char *path1, const char *path2) { Serial.printf("Renaming file %s to %s\n", path1, path2); if (fs.rename(path1, path2)) { Serial.println("File renamed"); } else { Serial.println("Rename failed"); } } void deleteFile(fs::FS &fs, const char *path) { Serial.printf("Deleting file: %s\n", path); if (fs.remove(path)) { Serial.println("File deleted"); } else { Serial.println("Delete failed"); } } void testFileIO(fs::FS &fs, const char *path) { File file = fs.open(path); static uint8_t buf[512]; size_t len = 0; uint32_t start = millis(); uint32_t end = start; if (file) { len = file.size(); size_t flen = len; start = millis(); while (len) { size_t toRead = len; if (toRead > 512) { toRead = 512; } file.read(buf, toRead); len -= toRead; } end = millis() - start; Serial.printf("%u bytes read for %lu ms\n", flen, end); file.close(); } else { Serial.println("Failed to open file for reading"); } file = fs.open(path, FILE_WRITE); if (!file) { Serial.println("Failed to open file for writing"); return; } size_t i; start = millis(); for (i = 0; i < 2048; i++) { file.write(buf, 512); } end = millis() - start; Serial.printf("%u bytes written for %lu ms\n", 2048 * 512, end); file.close(); } //------------ SD_MMC 1-wire SD mode ---------------------------- #define mmc_CMD 18 #define mmc_CLK 17 #define mmc_D0 16 #define SDMMC_FREQ 30000 #define MAX_FILE 3 void setup() { Serial.begin(115200); /* // If you want to change the pin assignment or you get an error that some pins // are not assigned on ESP32-S3/ESP32-P4 uncomment this block and the appropriate // line depending if you want to use 1-bit or 4-bit line. // Please note that ESP32 does not allow pin change and setPins() will always fail. //if(! SD_MMC.setPins(clk, cmd, d0)){ //if(! SD_MMC.setPins(clk, cmd, d0, d1, d2, d3)){ // Serial.println("Pin change failed!"); // return; //} */ // Initialise the SD_MMC pinMode(mmc_D0, INPUT_PULLUP); SD_MMC.setPins(mmc_CLK, mmc_CMD, mmc_D0); if(!SD_MMC.begin("/sdmmc", true, false, SDMMC_FREQ, MAX_FILE)){ Serial.println("Card Mount Failed"); return; } else Serial.println("SD_MMC initialisation OK"); uint8_t cardType = SD_MMC.cardType(); if (cardType == CARD_NONE) { Serial.println("No SD_MMC card attached"); return; } Serial.print("SD_MMC Card Type: "); if (cardType == CARD_MMC) { Serial.println("MMC"); } else if (cardType == CARD_SD) { Serial.println("SDSC"); } else if (cardType == CARD_SDHC) { Serial.println("SDHC"); } else { Serial.println("UNKNOWN"); } uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024); Serial.printf("SD_MMC Card Size: %lluMB\n", cardSize); listDir(SD_MMC, "/", 0); createDir(SD_MMC, "/mydir"); listDir(SD_MMC, "/", 0); removeDir(SD_MMC, "/mydir"); listDir(SD_MMC, "/", 2); writeFile(SD_MMC, "/hello.txt", "Hello "); appendFile(SD_MMC, "/hello.txt", "World!\n"); readFile(SD_MMC, "/hello.txt"); deleteFile(SD_MMC, "/foo.txt"); renameFile(SD_MMC, "/hello.txt", "/foo.txt"); readFile(SD_MMC, "/foo.txt"); testFileIO(SD_MMC, "/test.txt"); Serial.printf("Total space: %lluMB\n", SD_MMC.totalBytes() / (1024 * 1024)); Serial.printf("Used space: %lluMB\n", SD_MMC.usedBytes() / (1024 * 1024)); } void loop() { delay(10); }

コンパイルと実行

コンパイル前に、IDEを下記の様に設定して下さい。

Arduino IDE setup.
  • ボード: ESP32S3 Dev Module
  • USB CDC On Boot: Disabled
  • USB DFU On Boot: Disabled
  • PSRAM: Disabled

設定後、SDカードソケットにフォーマット後の何も書かれていないメディアを入れて コンパイル実行して下さい。モニタに結果が表示されます。赤字の部分がこのスケッチの出力です。

ESP-ROM:esp32s3-20210327 Build:Mar 27 2021 rst:0x1 (POWERON),boot:0x2b (SPI_FAST_FLASH_BOOT) SPIWP:0xee mode:DIO, clock div:1 load:0x3fce2820,len:0x116c load:0x403c8700,len:0xc2c load:0x403cb700,len:0x3108 entry 0x403c88b8 SD_MMC initialisation OK SD_MMC Card Type: SDSC SD_MMC Card Size: 1846MB Listing directory: / Creating Dir: /mydir Dir created Listing directory: / DIR : mydir Removing Dir: /mydir Dir removed Listing directory: / Writing file: /hello.txt File written Appending to file: /hello.txt Message appended Reading file: /hello.txt Read from file: Hello World! Deleting file: /foo.txt File deleted Renaming file /hello.txt to /foo.txt File renamed Reading file: /foo.txt Read from file: Hello World! 1048576 bytes read for 531 ms 1048576 bytes written for 1553 ms Total space: 1841MB Used space: 1MB

追加説明

*「テキストモード」と「バイナリーモード」が無い

標準Cライブラリのような書式 "rb" や "w" などが無い。すべて「バイナリー」として扱われる。 「テキスト」と「バイナリー」の区別はユーザが関数で使い分ける。

  • 目的使うメソッド挙動
    テキストprint(), println()数値を文字列に変換して書き込む(例:123"123"
    バイナリーwrite()データをそのままのバイト値で書き込む(例:0x7B
    読み取りread() / readBytes()1バイトずつ、またはまとめてバイト列として読み取る

* println() と print()

ESP32で println() を使うと、デフォルトでは \r\n (CRLF) が書き込まれる。 \n(LF)のみ書き込みたい時は print() を使う。

* ディレクトリに移動するという概念が無い。

ディレクトリを階層的に作成する事は出来るのですが、ディレクトリに移動する という概念が有りません。ディレクトリに有るファイルにアクセスする時は常にフルパス指定となります。

最後に

とにかく転送が速い事。MMC(1-Line)でもSPIのSDより倍位速く感じます。 それと使用ピンがMMC(1-Line)ならSPI SD より1本少ない事が利点と思います。 ただ、ノイズにはSPI SDより弱いです。そこには注意が必要です。

SINCE 2026